#!/usr/bin/env node
/**
 * WebShare 代理连通性测试脚本（Windows/Node）
 *
 * 用途：
 * - 通过 curl.exe 验证通过 WebShare 网关端口访问 HTTP 与 HTTPS 目标是否可用
 * - 输出状态码与出口 IP，辅助选择正确的端口/协议
 *
 * 使用示例（PowerShell）：
 *   node ./tools/test-webshare-proxy.mjs --host p.webshare.io --port 20000 --username <USER> --password <PASS>
 *   node ./tools/test-webshare-proxy.mjs --host p.webshare.io --port 80 --username <USER> --password <PASS>
 *   node ./tools/test-webshare-proxy.mjs --protocol socks5 --port 1080 --username <USER> --password <PASS>
 */

import { spawn } from 'node:child_process'
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'

function parseArgs(argv) {
  const out = {
    host: 'p.webshare.io',
    port: 80,
    username: '',
    password: '',
    protocol: 'http', // http | socks5
    proxyScheme: 'http', // http | https | socks5 (curl -x)
    httpUrl: 'http://httpbin.org/ip',
    httpsUrl: 'https://www.gov.hk/',
    timeout: 15000,
    verbose: true,
    auto: true,
  }
  for (let i = 2; i < argv.length; i++) {
    const a = argv[i]
    const [k, vRaw] = a.startsWith('--') ? a.slice(2).split('=') : [null, null]
    const v = (vRaw !== undefined ? vRaw : argv[i + 1])
    switch (k) {
      case 'host': out.host = String(v); i += (vRaw === undefined ? 1 : 0); break
      case 'port': out.port = Number(v); i += (vRaw === undefined ? 1 : 0); break
      case 'username': out.username = String(v); i += (vRaw === undefined ? 1 : 0); break
      case 'password': out.password = String(v); i += (vRaw === undefined ? 1 : 0); break
      case 'protocol': out.protocol = String(v); i += (vRaw === undefined ? 1 : 0); break
      case 'proxy-scheme': out.proxyScheme = String(v); i += (vRaw === undefined ? 1 : 0); break
      case 'http-url': out.httpUrl = String(v); i += (vRaw === undefined ? 1 : 0); break
      case 'https-url': out.httpsUrl = String(v); i += (vRaw === undefined ? 1 : 0); break
      case 'timeout': out.timeout = Number(v); i += (vRaw === undefined ? 1 : 0); break
      case 'quiet': out.verbose = false; break
      case 'verbose': out.verbose = true; break
      case 'no-auto': out.auto = false; break
      default: break
    }
  }
  return out
}

function runCurl(args, opts = {}) {
  return new Promise((resolve) => {
    const ps = spawn('curl.exe', args, { stdio: ['ignore', 'pipe', 'pipe'], shell: false })
    let stdout = ''
    let stderr = ''
    ps.stdout.on('data', (d) => { stdout += d.toString() })
    ps.stderr.on('data', (d) => { stderr += d.toString() })
    ps.on('close', code => resolve({ code, stdout, stderr, args }))
    if (opts.timeout && opts.timeout > 0) {
      setTimeout(() => {
        try { ps.kill('SIGKILL') }
        catch {}
      }, opts.timeout)
    }
  })
}

function buildProxyArgs(cfg) {
  const cred = `${encodeURIComponent(cfg.username)}:${encodeURIComponent(cfg.password)}`
  if (cfg.proxyScheme === 'socks5' || cfg.protocol === 'socks5')
    return [`--socks5-hostname`, `${cfg.host}:${cfg.port}`]
  if (cfg.proxyScheme === 'https')
    return ['-x', `https://${cred}@${cfg.host}:${cfg.port}`, '--proxy-insecure']
  return ['-x', `http://${cred}@${cfg.host}:${cfg.port}`]
}

async function testHttpViaWebshare(cfg) {
  const proxy = buildProxyArgs(cfg)

  const httpArgs = [
    '-sS',
    ...proxy,
    '--max-time',
    String(Math.ceil(cfg.timeout / 1000)),
    cfg.httpUrl,
  ]
  const httpsArgs = [
    '-sS',
    ...proxy,
    '--max-time',
    String(Math.ceil(cfg.timeout / 1000)),
    '-o',
    'NUL',
    '-w',
    'HTTP:%{http_code}\n',
    cfg.httpsUrl,
  ]

  if (cfg.verbose) {
    console.log(`HTTP 测试 -> ${cfg.httpUrl}`)
  }
  const r1 = await runCurl(httpArgs, { timeout: cfg.timeout + 2000 })
  if (cfg.verbose) {
    console.log(r1.stdout.trim() || '')
    if (r1.stderr)
      console.log(r1.stderr.trim())
  }

  if (cfg.verbose) {
    console.log(`HTTPS 测试 -> ${cfg.httpsUrl}`)
  }
  const r2 = await runCurl(httpsArgs, { timeout: cfg.timeout + 2000 })
  if (cfg.verbose) {
    console.log((r2.stdout || '').trim())
    if (r2.stderr)
      console.log(r2.stderr.trim())
  }

  const okHttp = r1.code === 0 && /\d+\.\d+\.\d+\.\d+/.test(r1.stdout)
  const okHttps = r2.code === 0 && /HTTP:(200|30[12])/.test(r2.stdout)

  const reason = (() => {
    if (okHttps)
      return 'OK'
    const e = `${r2.stderr || ''}\n${r2.stdout || ''}`.toLowerCase()
    if (e.includes('proxy connect aborted'))
      return 'CONNECT_NOT_SUPPORTED'
    if (e.includes('schannel'))
      return 'TLS_HANDSHAKE_FAILED'
    if (e.includes(' 407') || e.includes('proxy authentication'))
      return 'PROXY_AUTH_REQUIRED'
    if (e.includes('timeout') || e.includes('http:000'))
      return 'TIMEOUT'
    if (e.includes('connection to proxy closed'))
      return 'PROXY_CLOSED'
    return 'UNKNOWN'
  })()

  return { okHttp, okHttps, http: r1, https: r2, httpsReason: reason }
}

async function loadAppConfig() {
  try {
    const appData = process.env.APPDATA || ''
    if (!appData)
      return null
    const cfgPath = path.join(appData, 'AutoTools', 'config', 'default.json')
    const raw = await readFile(cfgPath, 'utf8')
    return JSON.parse(raw)
  }
  catch {
    return null
  }
}

function parseProxyEntryInline(entry, fallbackAuth, protocol = 'http') {
  const parts = String(entry || '').split(':')
  if (parts.length >= 4) {
    const host = parts[0]
    const port = Number(parts[1])
    const username = parts[2]
    const password = parts.slice(3).join(':')
    return { host, port, auth: { username, password }, protocol }
  }
  return { host: String(parts[0] || entry), port: Number(parts[1] || 0), auth: fallbackAuth, protocol }
}

function uniqBy(arr, keyFn) {
  const seen = new Set()
  const out = []
  for (const it of arr) {
    const k = keyFn(it)
    if (!seen.has(k)) { seen.add(k); out.push(it) }
  }
  return out
}

async function main() {
  const cfg = parseArgs(process.argv)
  // 自动模式：从 AppData 配置读取 provider 与凭据并批量测试常见端口
  if (cfg.auto) {
    const appCfg = await loadAppConfig()
    if (!appCfg || !appCfg.proxy) {
      console.error('未找到配置：%APPDATA%/AutoTools/config/default.json 或缺少 proxy 配置')
      process.exit(2)
    }

    const providers = Array.isArray(appCfg.proxy.providers) ? appCfg.proxy.providers : []
    const enabled = providers.filter(p => p && p.enabled !== false)
    if (enabled.length === 0) {
      console.error('未找到启用的 proxy.providers')
      process.exit(2)
    }

    const commonHttpPorts = [20000, 80, 31112, 8080]
    const httpsProxyPorts = [443, 8443, 9443]
    const socksPorts = [1080]
    const results = []

    for (const p of enabled) {
      const topAuth = (p.auth && p.auth.username) ? p.auth : ((appCfg.proxy.auth && appCfg.proxy.auth.username) ? appCfg.proxy.auth : null)
      if (!topAuth) {
        console.warn(`跳过 provider ${p.id || ''}: 缺少用户名/密码`)
        continue
      }
      const pProto = (p.protocol || appCfg.proxy.protocol || 'http')
      const pool = Array.isArray(p.pool) ? p.pool : []

      // 解析池中域名网关；若不存在，默认使用 p.webshare.io
      let gateways = pool
        .map(e => parseProxyEntryInline(e, topAuth, pProto))
        .filter(e => /[a-z]/i.test(e.host)) // 仅域名
      if (gateways.length === 0) {
        gateways = [{ host: 'p.webshare.io', port: 0, auth: topAuth, protocol: 'http' }]
      }

      for (const g of gateways) {
        const host = g.host
        const auth = g.auth
        // HTTP 端口组
        const portsToTry = g.port > 0 ? [g.port] : commonHttpPorts
        for (const port of portsToTry) {
          const testCfg = { ...cfg, host, port, username: auth.username, password: auth.password, protocol: 'http', proxyScheme: 'http' }
          console.log(`\n==> 测试 HTTP 端口: http://${host}:${port}`)
          const res = await testHttpViaWebshare(testCfg)
          results.push({ provider: p.id, proto: 'http-proxy', host, port, okHttp: res.okHttp, okHttps: res.okHttps, reason: res.httpsReason })
        }
        // HTTPS 代理端口（TLS到代理）
        for (const port of httpsProxyPorts) {
          const testCfg = { ...cfg, host, port, username: auth.username, password: auth.password, protocol: 'http', proxyScheme: 'https' }
          console.log(`\n==> 测试 HTTPS 代理端口: https://${host}:${port}`)
          const res = await testHttpViaWebshare(testCfg)
          results.push({ provider: p.id, proto: 'https-proxy', host, port, okHttp: res.okHttp, okHttps: res.okHttps, reason: res.httpsReason })
        }
        // SOCKS5 端口（若需要，可顺带测）
        for (const port of socksPorts) {
          const testCfg = { ...cfg, host, port, username: auth.username, password: auth.password, protocol: 'socks5', proxyScheme: 'socks5' }
          console.log(`\n==> 测试 SOCKS5 端口: socks5://${host}:${port}`)
          const res = await testHttpViaWebshare(testCfg)
          results.push({ provider: p.id, proto: 'socks5', host, port, okHttp: res.okHttp, okHttps: res.okHttps, reason: res.httpsReason })
        }
      }
    }

    const uniq = uniqBy(results, r => `${r.proto}|${r.host}|${r.port}`)
    console.log('\n===== 汇总 =====')
    uniq.forEach((r) => {
      const tag = `${r.proto}://${r.host}:${r.port}`
      const detail = r.okHttps ? '' : (r.reason ? ` (${r.reason})` : '')
      console.log(`${tag} -> HTTP:${r.okHttp ? 'OK' : 'FAIL'} HTTPS:${r.okHttps ? 'OK' : 'FAIL'}${detail}`)
    })

    const okAny = uniq.some(r => r.okHttp || r.okHttps)
    const okHttpsAny = uniq.some(r => r.okHttps)
    if (!okAny)
      process.exit(1)
    if (!okHttpsAny)
      process.exit(3) // 仅 HTTP 可用，HTTPS 不可用
    process.exit(0)
  }
  // 手动模式：使用命令行参数
  if (!cfg.username || !cfg.password) {
    console.error('缺少 --username/--password')
    process.exit(2)
  }
  console.log(`网关: ${cfg.protocol}://${cfg.host}:${cfg.port}`)
  const res = await testHttpViaWebshare(cfg)
  console.log('HTTP 结果:', res.okHttp ? 'OK' : 'FAIL')
  console.log('HTTPS 结果:', res.okHttps ? 'OK' : 'FAIL')
  if (!res.okHttp && !res.okHttps)
    process.exit(1)
}

main().catch((e) => {
  console.error('运行失败:', e?.message || e)
  process.exit(1)
})
